package com.bittech.httpd.core;

import com.bittech.httpd.common.Handler;
import com.bittech.httpd.common.HttpMethod;
import com.bittech.httpd.common.HttpRequest;
import com.bittech.httpd.common.HttpResponse;
import com.bittech.httpd.common.HttpStatus;
import com.bittech.httpd.common.SupportedMimeType;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 分发处理类
 * Author: secondriver
 * Created: 2018/9/5
 */
public final class DispatcherHandler {
    
    private Map<Class<? extends Handler>, Handler> classHandlerMap = new ConcurrentHashMap<>();
    
    private Map<String, Class<? extends Handler>> handlerClassMap = new ConcurrentHashMap<>();
    
    private final Map<String, Object> context;
    
    DispatcherHandler(Map<String, Object> context) {
        this.context = context;
    }
    
    public void register(String url, Class<? extends Handler> clazz) {
        this.register(Collections.singletonList(url), clazz);
    }
    
    public void register(String url, Handler handler) {
        this.classHandlerMap.put(handler.getClass(), handler);
        this.register(url, handler.getClass());
    }
    
    public void register(HttpStatus httpStatus, Class<? extends Handler> clazz) {
        this.register(String.valueOf(httpStatus.getRequestStatus()), clazz);
    }
    
    public void register(HttpStatus httpStatus, Handler handler) {
        this.classHandlerMap.put(handler.getClass(), handler);
        this.register(String.valueOf(httpStatus.getRequestStatus()), handler.getClass());
    }
    
    public void register(List<String> urls, Class<? extends Handler> clazz) {
        if (urls == null || urls.isEmpty()) {
            throw new IllegalArgumentException("urls must be not null.");
        }
        if (clazz == null) {
            throw new IllegalArgumentException("class must be not null.");
        }
        for (String url : urls) {
            this.handlerClassMap.put(url, clazz);
        }
    }
    
    public void register(List<String> urls, Handler handler) {
        this.classHandlerMap.put(handler.getClass(), handler);
        this.register(urls, handler.getClass());
    }
    
    public void register(String[] urls, Class<? extends Handler> clazz) {
        if (urls == null || urls.length == 0) {
            throw new IllegalArgumentException("urls must be not null or empty.");
        }
        this.register(Arrays.asList(urls), clazz);
    }
    
    public void register(String[] urls, Handler handler) {
        this.classHandlerMap.put(handler.getClass(), handler);
        this.register(urls, handler.getClass());
    }
    
    /**
     * 根据请求URL获取处理器
     *
     * @param url
     * @return
     */
    private synchronized Optional<Handler> obtainHandler(String url) {
        Class<? extends Handler> clazz = this.handlerClassMap.get(url);
        if (clazz != null) {
            Handler handle = this.classHandlerMap.get(clazz);
            if (handle != null) {
                return Optional.of(handle);
            } else {
                try {
                    handle = clazz.getConstructor().newInstance();
                    this.classHandlerMap.put(clazz, handle);
                    return Optional.of(handle);
                } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        return Optional.empty();
    }
    
    /**
     * 根据状态码获取处理器
     *
     * @param httpStatus
     * @return
     */
    private Optional<Handler> obtainHandler(HttpStatus httpStatus) {
        return obtainHandler(String.valueOf(httpStatus.getRequestStatus()));
    }
    
    
    /**
     * 处理逻辑
     *
     * @param socket
     * @return
     */
    Runnable handler(final Socket socket) {
        return () -> {
            HttpReqRespWrapper wrapper = new HttpReqRespWrapper(socket);
            DefaultHttpRequest request = (DefaultHttpRequest) wrapper.request();
            request.setContext(context);
            DefaultHttpResponse response = (DefaultHttpResponse) wrapper.response();
            response.setContext(context);
            try {
                if (request.uri() == null) {
                    this.obtainHandler(HttpStatus.NOT_FOUND)
                            .ifPresent(handler -> handler.doGet(request, response));
                    return;
                }
                if (request.method() == null ||
                        HttpMethod.lookup(request.method().name()) == null) {
                    this.obtainHandler(HttpStatus.METHOD_NOT_ALLOWED)
                            .ifPresent(handler -> handler.doGet(request, response));
                    return;
                }
                String uri = request.uri();
                if (isStaticResource(uri)) {
                    this.obtainHandler(HttpServer.STATIC_URL)
                            .ifPresent(handler ->
                                    handler.doGet(request, response)
                            );
                } else {
                    Optional<Handler> handlerOptional = this.obtainHandler(uri);
                    if (handlerOptional.isPresent()) {
                        HttpMethod method = request.method();
                        if (method == HttpMethod.GET) {
                            handlerOptional.get().doGet(request, response);
                        } else if (method == HttpMethod.POST) {
                            handlerOptional.get().doPost(request, response);
                        } else {
                            this.obtainHandler(HttpStatus.METHOD_NOT_ALLOWED)
                                    .ifPresent(handler -> handler.doGet(request, response));
                        }
                    } else {
                        this.obtainHandler(HttpStatus.NOT_FOUND)
                                .ifPresent(handler -> handler.doGet(request, response));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                this.obtainHandler(HttpStatus.INTERNAL_ERROR)
                        .ifPresent(handler -> handler.doGet(request, response));
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
    }
    
    private static boolean isStaticResource(String url) {
        return SupportedMimeType.lookup(url.substring(url.lastIndexOf(".") + 1)) != null;
    }
    
    
    public static class HttpReqRespWrapper {
        
        private HttpRequest request;
        
        private HttpResponse response;
        
        private final Socket socket;
        
        
        public HttpReqRespWrapper(Socket socket) {
            this.socket = socket;
            this.parseHandler();
        }
        
        
        public HttpRequest request() {
            return request;
        }
        
        public HttpResponse response() {
            return response;
        }
        
        public void parseHandler() {
            Map<String, String> line = new HashMap<>();
            Map<String, String> header = new HashMap<>();
            Map<String, List<String>> params = new HashMap<>();
            try {
                InputStream inputStream = socket.getInputStream();
                InputStreamReader buffer = new InputStreamReader(inputStream);
                BufferedReader reader = new BufferedReader(buffer);
                
                //1. 读取相应首行
                String lineValue = reader.readLine();
                StringTokenizer tokenizer = null;
                if (lineValue != null) {
                    //GET /index?key1=value1&key2=value2 HTTP/1.1
                    tokenizer = new StringTokenizer(lineValue);
                }
                if (tokenizer != null && tokenizer.hasMoreTokens()) {
                    String method = tokenizer.nextToken();
                    line.put("method", method);
                }
                if (tokenizer != null && tokenizer.hasMoreTokens()) {
                    String url = tokenizer.nextToken();
                    int index = url.indexOf("?");
                    if (index != -1) {
                        params = decodeParameters(url.substring(index + 1));
                        line.put("url", url.substring(0, index));
                    } else {
                        line.put("url", url);
                    }
                }
                if (tokenizer != null && tokenizer.hasMoreTokens()) {
                    String version = tokenizer.nextToken();
                    line.put("version", version);
                }
                
                //2. 读取响应头部信息
                lineValue = reader.readLine();
                if (lineValue != null) {
                    while (!(lineValue.trim().isEmpty())) {
                        int index = lineValue.indexOf(":");
                        String key = lineValue.substring(0, index);
                        String value = lineValue.substring(index + 1);
                        header.put(key, value);
                        lineValue = reader.readLine();
                    }
                }
                //3. 读取空行
                //4. 读取响应体
                this.request = new DefaultHttpRequest(line, header, params);
                this.response = new DefaultHttpResponse(socket, request);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        private static Map<String, List<String>> decodeParameters(String queryString) {
            Map<String, List<String>> params = new HashMap<>();
            //key1=value1&key2=value2&key1=value5
            if (queryString != null) {
                StringTokenizer tokenizer = new StringTokenizer(queryString, "&");
                while (tokenizer.hasMoreTokens()) {
                    String kv = tokenizer.nextToken();
                    int index = kv.indexOf("=");
                    String key = decodePercent(kv.substring(0, index));
                    String value = decodePercent(kv.substring(index + 1));
                    if (params.containsKey(key)) {
                        List<String> values = params.get(key);
                        values.add(value);
                    } else {
                        List<String> values = new ArrayList<>();
                        values.add(value);
                        params.put(key, values);
                    }
                }
            }
            return params;
        }
        
        static String decodePercent(String str) {
            String decoded = null;
            try {
                decoded = URLDecoder.decode(str, "UTF-8");
            } catch (UnsupportedEncodingException ignored) {
            }
            return decoded;
        }
    }
}